﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace GameBoardViewer.Board3D
{
    public class RayTracer
    {

        public static Microsoft.Xna.Framework.Point MouseCoordinatesToTextureCoordinates(int mouseX, int mouseY, Model model,
                        Matrix projection, Matrix view, Matrix world, Texture2D texture, Viewport wp, ref Vector3 collisionVector)
        {
            // define the ray
            Vector3 pos1 = wp.Unproject(new Vector3(mouseX, mouseY, 0), projection, view, world);
            Vector3 pos2 = wp.Unproject(new Vector3(mouseX, mouseY, 1), projection, view, world);
            Vector3 ray = Vector3.Normalize(pos1 - pos2);

            // do ray  testing against the model
            IntersectionResult resultTest = RayModelTest(ray, pos2, model);
            if (resultTest != null)
            {
                float u = MathHelper.Barycentric(
                        resultTest.FaceVertices[0].Texture.X,
                        resultTest.FaceVertices[1].Texture.X,
                        resultTest.FaceVertices[2].Texture.X,
                        resultTest.Intersection.U,
                        resultTest.Intersection.V);
                float v = MathHelper.Barycentric(
                        resultTest.FaceVertices[0].Texture.Y,
                        resultTest.FaceVertices[1].Texture.Y,
                        resultTest.FaceVertices[2].Texture.Y,
                        resultTest.Intersection.U,
                        resultTest.Intersection.V);

                float x = MathHelper.Barycentric(
                        resultTest.FaceVertices[0].Position.X,
                        resultTest.FaceVertices[1].Position.X,
                        resultTest.FaceVertices[2].Position.X,
                        resultTest.Intersection.U,
                        resultTest.Intersection.V);

                float y = MathHelper.Barycentric(
                        resultTest.FaceVertices[0].Position.Y,
                        resultTest.FaceVertices[1].Position.Y,
                        resultTest.FaceVertices[2].Position.Y,
                        resultTest.Intersection.U,
                        resultTest.Intersection.V);

                float z = MathHelper.Barycentric(
                        resultTest.FaceVertices[0].Position.Z,
                        resultTest.FaceVertices[1].Position.Z,
                        resultTest.FaceVertices[2].Position.Z,
                        resultTest.Intersection.U,
                        resultTest.Intersection.V);

                collisionVector = new Vector3(x, y, z);
                 
                return new Microsoft.Xna.Framework.Point((int)(texture.Width * u), (int)(texture.Height * v));
            }

            return Microsoft.Xna.Framework.Point.Zero;
        }

        protected static IntersectionResult RayModelTest(Vector3 ray, Vector3 pos, Model model)
        {
            ModelDecl decl = ModelParser.ParseModel(model);

            foreach (var mm in decl.ModelMesh)
            {
                foreach (var mmp in mm.ModelMeshPart)
                {
                    // iterate through faces
                    foreach (var index in mmp.Indexes)
                    {
                        Intersection result = RayTriangleTest(ray, pos,
                                mmp.Vertexes[index.Index1].Position,
                                mmp.Vertexes[index.Index2].Position,
                                mmp.Vertexes[index.Index3].Position);

                        if (result != null)
                        {
                            IntersectionResult res = new IntersectionResult();
                            res.FaceVertices.Add(mmp.Vertexes[index.Index1]);
                            res.FaceVertices.Add(mmp.Vertexes[index.Index2]);
                            res.FaceVertices.Add(mmp.Vertexes[index.Index3]);
                            res.Intersection = result;

                            return res;
                        }
                    }
                }
            }

            return null;
        }


        protected static Intersection RayTriangleTest(Vector3 ray, Vector3 pos, Vector3 p0, Vector3 p1, Vector3 p2)
        {
            // find edges
            Vector3 edge1 = p1 - p0;
            Vector3 edge2 = p2 - p0;

            float epsilon = 0.000001f;

            Vector3 pvec = Vector3.Cross(ray, edge2);

            float det = Vector3.Dot(edge1, pvec);

            // vector lies in the plane of triangle
            if (det < epsilon)
                return null;

            // calculate distance from the origin
            Vector3 tvec = pos - p0;

            // calculate U
            float u = Vector3.Dot(tvec, pvec);
            if (u < 0.0f || u > det)
                return null;

            // calculate V
            Vector3 qvec = Vector3.Cross(tvec, edge1);
            float v = Vector3.Dot(ray, qvec);
            if (v < 0 || u + v > det)
                return null;

            float t = Vector3.Dot(edge2, qvec);

            // do scaling
            float inv_det = 1.0f / det;
            t *= inv_det;
            u *= inv_det;
            v *= inv_det;

            return new Intersection(u, v, t);
        }   
    }
}
